package beer.coder.walawala.gui.handler;

import beer.coder.walawala.common.constant.Constant;
import beer.coder.walawala.gui.util.ProcessUtil;
import beer.coder.walawala.gui.util.StageUtil;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import javafx.stage.Stage;
import org.apache.commons.lang3.StringUtils;

/**
 * 应用进程处理器.
 * 在重复启动应用，则与已启动的应用通过socket交互，唤醒已启动的应用。
 *
 * @author amath0312
 * @version 1.0
 * @date 2019-10-31 20:37
 */
public class WaProcessHandler {

  public static final String CMD_SHOW = "show";
  public static final String CMD_PING = "ping";
  public static final String CMD_PING_RESP = "pong";
  private Stage stage;
  private String pidFile;
  private int listenPort;
  Thread commandHandler;

  public WaProcessHandler(String pidFile, int listenPort) {
    this.pidFile = pidFile;
    this.listenPort = listenPort;
  }

  /**
   * 初始化当前应用.
   */
  public void initialProcess(Stage stage) throws IOException {
    this.stage = stage;
    savePid();
    commandHandler = new Thread(this::listenCommand);
    commandHandler.setDaemon(true);
    commandHandler.start();
  }

  /**
   * 查找是否有已启动窗口.
   */
  public boolean isConflict() throws IOException {
    if (!ping()) {
      File pidFile = new File(System.getProperty("user.dir"), this.pidFile);
      if (pidFile.exists() && pidFile.isFile()) {
        String pid = null;
        try (BufferedReader reader = new BufferedReader(
            new InputStreamReader(new FileInputStream(pidFile), Constant.ENCODING))) {
          pid = reader.readLine();
        }

        return StringUtils.isEmpty(pid);
      } else {
        return false;
      }
    }
    return true;
  }


  public void showExistedProcess() throws IOException {
    DatagramSocket client = new DatagramSocket();
    byte[] data = CMD_SHOW.getBytes(Constant.ENCODING);
    DatagramPacket command = new DatagramPacket(
        data,
        data.length,
        new InetSocketAddress(Inet4Address.getLoopbackAddress(), this.listenPort));
    client.send(command);
    client.close();
  }

  public boolean ping() {
    DatagramSocket client = null;
    try {
      client = new DatagramSocket(this.listenPort);
      return false;
    } catch (IOException e) {
      return true;
    } finally {
      if (client != null) {
        client.close();
      }
    }
  }


  private void savePid() throws IOException {
    int pid = ProcessUtil.currentProcessId();
    File pidFile = new File(System.getProperty("user.dir"), "data/pid.lck");
    Files.deleteIfExists(pidFile.toPath());
    if (pidFile.getParentFile() != null && !pidFile.getParentFile().exists()) {
      pidFile.getParentFile().mkdirs();
    }
    try (FileOutputStream fos = new FileOutputStream(pidFile)) {
      fos.write(String.valueOf(pid).getBytes(Constant.ENCODING));
    }
    pidFile.deleteOnExit();
  }

  private void listenCommand() {
    DatagramSocket server = null;
    try {
      server = new DatagramSocket(new InetSocketAddress(Inet4Address.getLoopbackAddress(), this.listenPort));
      DatagramPacket command = new DatagramPacket(new byte[2048], 2048);

      while (true) {
        server.receive(command);
        if (StringUtils.equalsIgnoreCase(CMD_SHOW,
            new String(command.getData(), command.getOffset(), command.getLength(), Constant.ENCODING))) {
          StageUtil.showStage(this.stage);
        } else if (StringUtils.equalsIgnoreCase(CMD_PING,
            new String(command.getData(), command.getOffset(), command.getLength(), Constant.ENCODING))) {
          byte[] resp = CMD_PING_RESP.getBytes(Constant.ENCODING);
          server.send(new DatagramPacket(resp, resp.length, command.getAddress(), command.getPort()));
        }
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (server != null) {
        server.close();
      }
    }
  }

}
